package com.linkoog.devtools.code_generator.wizard.model.json;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.linkoog.devtools.intellij.wizard.WizardContext;
import com.linkoog.devtools.intellij.wizard.WizardStep;
import com.linkoog.devtools.code_generator.wizard.ModelWizardContext;
import com.linkoog.devtools.code_generator.common.JavaType;
import com.linkoog.devtools.code_generator.wizard.model.ModelJsonTreeTable;
import com.intellij.json.JsonLanguage;
import com.intellij.lang.Language;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.linkoog.devtools.utils.formatter.JsonFormatter;
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.*;
import java.util.Iterator;
import java.util.Map;

public class GenModelJsonStep extends WizardStep {

    private Project project;
    private JPanel myMainPanel;
    private JButton formatButton;
    private JButton clearButton;
    private JPanel jsonPanel;

    private FileEditor jsonBodyEditor;

    public GenModelJsonStep(Project project, WizardContext wizardContext) {
        super(project,wizardContext);
        this.project = project;
        this.myMainPanel.setMinimumSize(new Dimension(770, 550));

        initBottons();
        initJsonPanel();
    }

    private void initBottons(){
        formatButton.addActionListener(e -> {
            String json = getStringFromEditor();
            setEditorDoc(JsonFormatter.pretty(json));
        });

        clearButton.addActionListener(e -> {
            setEditorDoc(null);
        });
    }

    /**
     * set content for editor
     */
    private void setEditorDoc(String text) {
        WriteCommandAction.runWriteCommandAction(project, () -> {
            Document doc = getDocumentFromEditor(jsonBodyEditor);
            doc.deleteString(0, doc.getTextLength());
            doc.setText(text == null ? "" : text.replaceAll("\\r", ""));
        });
    }

    private String getStringFromEditor() {
        Document doc = getDocumentFromEditor(jsonBodyEditor);
        return doc.getText();
    }

    private Document getDocumentFromEditor(FileEditor editor) {
        return FileDocumentManager.getInstance().getCachedDocument(editor.getFile());
    }

    private void initJsonPanel(){
        SwingUtilities.invokeLater(() -> {
            jsonBodyEditor = createEditor(JsonLanguage.INSTANCE.getID(), null);
            jsonPanel.add(jsonBodyEditor.getComponent(), BorderLayout.CENTER);
        });
    }

    /**
     * create editor with specified language and content
     */
    private FileEditor createEditor(String languageId, String text) {
        if (StringUtils.isEmpty(text)) {
            text = "";
        }

        Language language = Language.findLanguageByID(languageId);
        PsiFile psiFile = PsiFileFactory.getInstance(project).createFileFromText(language, text);
        FileEditor editor = TextEditorProvider.getInstance().createEditor(project, psiFile.getVirtualFile());
        return editor;
    }

    public JsonNode toJsonNode(String jsonAsText) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, true);

        return mapper.readValue(jsonAsText, JsonNode.class);
    }

    @Override
    public boolean validate() throws ConfigurationException {
        String json = getStringFromEditor();
        JsonNode jsonNode = null;
        try {
            jsonNode = toJsonNode(json);
        } catch (Exception e) {
            throw new ConfigurationException("请输入正确的Json数据！", "错误");
        }
        if (!jsonNode.isObject()){
            throw new ConfigurationException("请输入Json对象！", "错误");
        }

        if (jsonNode.size() == 0){
            throw new ConfigurationException("没有字段！", "错误");
        }

        ModelJsonTreeTable.ItemInfo rootNode = ModelJsonTreeTable.createRootNode();
        Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
        while (fields.hasNext()) {
            Map.Entry<String, JsonNode> nodeEntry = fields.next();
            parse(rootNode, nodeEntry);
        }

        ModelWizardContext modelWizardContext = (ModelWizardContext) getWizardContext();
        modelWizardContext.setRoot(rootNode);
        return true;
    }

    private void parse(ModelJsonTreeTable.ItemInfo parent, Map.Entry<String, JsonNode> nodeEntry){
        JsonNode jsonNode = nodeEntry.getValue();
        ModelJsonTreeTable.ItemInfo field = new ModelJsonTreeTable.ItemInfo();
        field.setName(nodeEntry.getKey());
        field.setNameEditable(true);
        field.setChecked(true);
        field.setCheckEditable(true);
        JavaType type = parseJavaType(jsonNode);
        field.setType(type);
        field.setRemark("");
        parent.add(field);

        if (JavaType.LIST  == type) {
            JsonNode listNode = jsonNode.get(0);
            ModelJsonTreeTable.ItemInfo item = new ModelJsonTreeTable.ItemInfo();
            item.setName("Items");
            item.setNameEditable(false);
            item.setChecked(true);
            item.setCheckEditable(false);
            item.setRemark("");

            if (listNode == null){
                item.setType(JavaType.STRING);
            } else {
                JavaType listType = parseJavaType(listNode);
                item.setType(listType);
                if (JavaType.OBJECT == listType) {
                    parseObjectNode(listNode, item, listType);
                }
            }
            field.add(item);
        } else if (JavaType.OBJECT == type) {
            parseObjectNode(jsonNode, field, type);
        }
    }

    private void parseObjectNode(JsonNode jsonNode, ModelJsonTreeTable.ItemInfo item, JavaType listType) {
        if (JavaType.OBJECT == listType) {
            Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> childEntry = fields.next();
                parse(item, childEntry);
            }
        }
    }

    private JavaType parseJavaType(JsonNode jsonNode){
        JsonNodeType nodeType = jsonNode.getNodeType();
        if (JsonNodeType.ARRAY == nodeType){
            return JavaType.LIST;
        } else if (JsonNodeType.OBJECT == nodeType || JsonNodeType.POJO == nodeType){
            return JavaType.OBJECT;
        } else if (JsonNodeType.BOOLEAN == nodeType){
            return JavaType.BOOLEAN;
        } else if (JsonNodeType.STRING == nodeType){
            return JavaType.STRING;
        }else if (JsonNodeType.NUMBER == nodeType){
            if (jsonNode.canConvertToInt()){
                return JavaType.INTEGER;
            } else if (jsonNode.canConvertToLong()){
                return JavaType.LONG;
            }
            return JavaType.BIGDECIMAL;
        }
        return JavaType.STRING;
    }

    @Override
    public void onStepOK() {
    }

    @Override
    public JPanel getComponent() {
        return myMainPanel;
    }
}
